﻿namespace MefContrib.Models.Provider
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Reflection;

    /// <summary>
    /// 
    /// </summary>
    public static class MethodInfoExtensions
    {
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private static readonly Type[] knownTypesWithReturn = { typeof(Func<>), typeof(Func<,>), typeof(Func<,,>), typeof(Func<,,,>), typeof(Func<,,,,>) };

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private static readonly Type[] knownTypesWithoutReturn = { typeof(Action), typeof(Action<>), typeof(Action<,>), typeof(Action<,,>), typeof(Action<,,,>) };

        /// <summary>
        /// 
        /// </summary>
        /// <param name="method"></param>
        /// <param name="instance"></param>
        /// <returns></returns>
        public static Delegate CreateDelegate(this MethodInfo method, object instance)
        {
            if (method.GetType().IsGenericType)
            {
                throw new InvalidOperationException("Unable to create a delegate for a method with generic arguments.");
            }

            List<ParameterInfo> parameters =
                method.GetParameters().ToList();

            if (parameters.Count > 4)
            {
                throw new InvalidOperationException("Unable to create a delegate for a method with more than 4 parameters.");
            }

            bool hasReturnValue =
                method.ReturnType != typeof(void);

            Type[] availableTypes = (hasReturnValue) ?
                knownTypesWithReturn : knownTypesWithoutReturn;

            Type delegateType =
                availableTypes[parameters.Count];

            List<Type> geneticArgumenTypes = new List<Type>();
            parameters.ForEach(info =>
                geneticArgumenTypes.Add(info.ParameterType));

            if (hasReturnValue)
            {
                geneticArgumenTypes.Add(method.ReturnType);
            }

            Type resultingType = (delegateType.IsGenericType) ?
                delegateType.MakeGenericType(geneticArgumenTypes.ToArray()) : delegateType;

            Delegate methodWrapper =
                Delegate.CreateDelegate(resultingType, instance, method);

            return methodWrapper;
        }
    }
}
